/**
 * Live Templates main object
 **/
var LiveTemplates = new function()
{
    /*
     * HTTP request helper
     **/

    var available_requests = []
    var loaded_files       = {}
    var GetRequest = function()
    {
        if ( available_requests.length == 0 )
        {
            return new XMLHttpRequest();
        }
        else
        {
            return available_requests.pop()
        }
    }
    var LoadFile = function(Location, OnLoad, Method)
    {
        if ( loaded_files[Location] )
        {
            OnLoad(loaded_files[Location])
        }
        else
        {
            var req    = GetRequest()
            var method = Method || "GET"
            req.open(method, Location, true)
            req.onreadystatechange = function()
            {
                if ( req.readyState == 4 )
                {
                    OnLoad(req.responseText)
                    loaded_files[Location] = req.responseText
                }
                available_requests.push(req)
            }
            req.send(null)
        }
    }
    
    var AppendBlock = function(Block, BeforeNode)
    {
        if ( Block.fragment.first_node ) this.insertBefore(Block.fragment, BeforeNode)
        else if ( !Block.first_node ) Block.Create(this)
        else
        {
            var current_node = Block.first_node
            var next_node    = Block.last_node.nextSibling
            while ( current_node != next_node )
            {
                var next_sibling = current_node.nextSibling || null
                this.insertBefore(current_node, BeforeNode)
                current_node = next_sibling
            }
        }
        return this
    }
    
    var AppendNode = function(Node)
    {
        this.appendChild(Node)
        return this
    }
    
    var RemoveBlock = function(Block)
    {
        Block.Remove()
    }
    
    var SetText = function(Text)
    {
        this.appendChild(document.createTextNode(Text))
        return this
    }

    var SetLiveText = function(LiveText)
    {
        this.appendChild(LiveText.node)
        return this
    }

    var SetAttributes = function(Attributes)
    {
        for ( var name in Attributes )
        {
            if ( typeof Attributes[name] == 'object' )
            {
                new LiveAttribute(this, name, Attributes[name])
            }
            else
            {
                this.setAttribute(name, Attributes[name])
            }
        }
        return this
    }
    
    
   
    // Figure out what browser is being used
    var b = navigator.userAgent.toLowerCase()
	this.browser = {
		safari: /webkit/.test(b),
		opera: /opera/.test(b),
		msie: /msie/.test(b) && !/opera/.test(b),
		mozilla: /mozilla/.test(b) && !/(compatible|webkit)/.test(b)
	}
    var NewNode
    var NewFragment
    if ( this.browser.msie )
    {
        NewNode = function(Type)
        {
            var new_node = document.createElement(Type)
            new_node.AppendBlock   = new_node.b = AppendBlock
            new_node.AppendNode    = new_node.a = AppendNode
            new_node.SetAttributes = new_node.s = SetAttributes
            new_node.SetText       = new_node.t = SetText
            new_node.NewNode       = new_node.n = NewNode
            return new_node
        }
        NewFragment = function()
        {
            var new_fragment = document.createDocumentFragment()
            new_fragment.AppendBlock   = new_fragment.b = AppendBlock
            new_fragment.AppendNode    = new_fragment.a = AppendNode
            new_fragment.SetAttributes = new_fragment.s = SetAttributes
            new_fragment.SetText       = new_fragment.t = SetText
        }
    }
    else
    {
        NewFragment = function()
        {
            return document.createDocumentFragment()
        }
        NewNode = function(Type)
        {
            return document.createElement(Type)
        }
        Element.prototype.AppendBlock   = Element.prototype.b = DocumentFragment.prototype.b = AppendBlock
        Element.prototype.AppendNode    = Element.prototype.a = DocumentFragment.prototype.a = AppendNode
        Element.prototype.SetAttributes = Element.prototype.s = DocumentFragment.prototype.s = SetAttributes
        Element.prototype.SetText       = Element.prototype.t = DocumentFragment.prototype.t = SetText
        Element.prototype.SetLiveText   = Element.prototype.l = DocumentFragment.prototype.l = SetLiveText
        Element.prototype.NewNode       = Element.prototype.n = DocumentFragment.prototype.n = NewNode
        Element.prototype.removeBlock   = RemoveBlock
    }
    
    /**
     * Live Variable Class
     */
    var GetLiveVar = function(Value)
    {
        var live_var
        if ( typeof(Value) == 'object' && Value.length != undefined )
        {
            live_var = new LiveArray(Value)
        }
        else if ( typeof(Value) == 'number' )
        {
            live_var = new LiveNumber()
        }
        else if ( typeof(Value) == 'string' )
        {
            live_var = new LiveString(Value)
        }
        else if ( typeof(Value) == 'object' )
        {
            live_var = new LiveArray(Value)
        }
        else if ( typeof(Value) == 'boolean' )
        {
            live_var = new LiveBoolean(Value)
        }
        else if ( typeof(Value) == 'date' )
        {
            live_var = new LiveDate(Value)
        }
        else if ( typeof(Value) == 'function' )
        {
            live_var = new LiveFunction(Value)
        }
        else
        {
            live_var = Value
        }
        live_var.registered_objects = {}
        return live_var
    }


    var last_live_var_id = 1
    var GetLiveVarId = function()
    {
        return last_live_var_id++
    }
    var LiveVar = function()
    {
        this.registered_objects = {}
        this.value = null
    }
    LiveVar.prototype.Register = function(LiveObject)
    {
        if ( !LiveObject.id ) LiveObject.id = GetLiveObjectId()
        if ( !this.registered_objects[LiveObject.id] ) this.registered_objects[LiveObject.id] = LiveObject
        return this.value
    }
    LiveVar.prototype.r = LiveVar.prototype.Register
    LiveVar.prototype.UnRegister = function(LiveObject)
    {
        this.registered_objects[LiveObject.id] = null
        delete this.registered_objects[LiveObject.id]
    }
    LiveVar.prototype.UpdateObjects = function(Value)
    {
        for ( var i in this.registered_objects )
        {
            this.registered_objects[i].Set(Value)
        }
    }
    LiveVar.prototype.valueOf = function()
    {
        return this.value
    }
    LiveVar.prototype.toString = function()
    {
        return this.value.toString()
    }
    LiveVar.prototype.Get = function()
    {
        return this.value
    }
    LiveVar.prototype.Set = function(Value)
    {
        this.value = Value
        this.UpdateObjects(Value)
    }
    var LiveString = function(Value)
    {
        if (Value) this.Set(Value)
    }
    LiveString.prototype = new LiveVar()

    var LiveNumber = function(Value)
    {
        if (Value) this.Set(Value)
    }
    LiveNumber.prototype = new LiveVar()
    /*
     * Live Array Class
     */
    var LiveArray = function(Value)
    {
        this.value = []
        if ( Value ) this.Set(Value)
    }
    LiveArray.prototype = new LiveVar()
    LiveArray.prototype.Set = function(Value)
    {
        for ( var i in Value )
        {
            this[i] = this.value[i] = GetLiveVar(Value[i])
            this[i].live_var_id = GetLiveVarId()
            //this.watch(i, this.WatchFunction)
        }
    }
    LiveArray.prototype.UpdateObjects = function(ChangeType, Index, Value)
    {
        for ( var i in this.registered_objects )
        {
            this.registered_objects[i][ChangeType](Index, Value)
        }
    }
    LiveArray.prototype.WatchFunction = function(Index, CurrentLiveVar, Value)
    {
        this.value[Index].Set(Value)
        return this.value[Index]
    }

    LiveArray.prototype.Add = function()
    {
        var index, value
        if ( arguments.length == 1 )
        {
            value = arguments[0]
            index = this.value.length
        }
        else if ( arguments.length == 2 )
        {
            index = arguments[0]
            value = arguments[1]
        }

        this[index] = this.value[index] = GetLiveVar(value)
        this.value[index].Set(value)
        this[index].live_var_id = GetLiveVarId()
        /* Now watching the variable, so any direct assignations call the watch function */
        //this.watch(index, this.WatchFunction)
        this.UpdateObjects('Add', index, value)
    }

    LiveArray.prototype.Delete = function(i)
    {
        /* Unwatching and deleting previos */
        //this.unwatch(i)
        delete this[i]
        delete this.value[i]
    }

    /*
     * Initializing main Live Var
     **/
    
    var $ = new LiveArray()
    
    var last_live_object_id = 1
    var GetLiveObjectId = function()
    {
        return last_live_var_id++
    }

    /*
     * Starting LiveObject base class
     **/
    var LiveObject = function()
    {
        this.$ = $
        this.id = null
        this.live_var = null
    }
    LiveObject.prototype.Delete = function()
    {
        if (this.live_var) this.live_var.UnRegister(this)
    }
    LiveObject.prototype.Set       = function(){}
    LiveObject.prototype.SetParent = function(ParentObject)
    {
        ParentObject.RegisterLiveObject(this)
        return this
    }

    /*
     * Starting the live Expression
     **/
    var LiveExpression = function(GetExpression)
    {
        this.GetExpression = GetExpression
        this.value = this.GetExpression.apply(this)
    }
    LiveExpression.prototype = new LiveObject()
    LiveExpression.prototype.Register = function(LiveObject)
    {
        this.live_object = LiveObject
    }
    LiveExpression.prototype.Set = function()
    {
        var new_value = this.GetExpression()
        if ( this.value != new_value )
        {
            this.value = new_value
            this.live_object.Set(this.value)
        }
    }
    LiveExpression.prototype.UnRegister = function()
    {
        this.live_object = null
    }
    LiveExpression.prototype.Delete = function()
    {
        this.live_var.UnRegister(this)
    }

    /*
     * Adding live attribute
     **/
    var LiveAttribute = function(AttributeName, LiveVariable)
    {
        this.name = AttributeName
        this.value = LiveVariable.Register(this)
    }
    LiveAttribute.prototype = new LiveObject()
    LiveAttribute.prototype.Create = function(Node)
    {
        this.node = Node
        this.node.setAttribute(this.name, this.value)
    }
    LiveAttribute.prototype.Set = function(Value)
    {
        this.node.setAttribute(this.name, Value)
    }

    /*
     * Live Text Object
     **/

    var LiveText = function(LiveVariable)
    {
        this.live_var = LiveVariable
        var value = LiveVariable.Register(this)
        this.node = document.createTextNode(value)
    }
    LiveText.prototype = new LiveObject()
    LiveText.prototype.Set = function(Value)
    {
        this.node.data = Value
    }


    /**
     * Live Block Class
     * A live block contains a group of one or more nodes
     * Live blocks "manage" a group of nodes, live or normal.
     * This nodes are created, updated and removed based on the live block type, and a live variable passed to it.
     */
    
    var LiveBlock = function(CreateNodes)
    {
        if ( CreateNodes ) this.CreateNodes = CreateNodes
        this.$  = $
        this.registered_objects = []
        this.first_node   = null
        this.last_node    = null
        this.live_var     = null
        this.fragment     = NewFragment()
    }
    LiveBlock.prototype.RegisterVar = function(LiveVariable)
    {
        if ( LiveVariable.Register )
        {
            this.live_var = LiveVariable
            this.value = LiveVariable.Register(this)
        }
        else
        {
            this.value = LiveVariable
        }
    }
    LiveBlock.prototype.Create = function(ParentNode)
    {
        var parent_node = ParentNode || this.fragment
        var last_node = parent_node.lastChild
        this.CreateNodes(parent_node)
        if ( parent_node.lastChild != last_node )
        {
            this.first_node = ( last_node ) ? last_node.nextSibling : parent_node.firstChild
            this.last_node  = parent_node.lastChild
            if ( ParentNode && ParentNode != parent_node ) ParentNode.appendChild(parent_node)
        }
        else
        {
            this.first_node = document.createComment('Empty LiveBlock placeholder for LiveBlock with id: '+this.id)
            parent_node.appendChild(this.first_node)
            this.last_node = this.first_node
        }
    }
    LiveBlock.prototype.Set = function(Value)
    {
        this.value = Value
        var before_node   = this.last_node.nextSibling || null
        var parent_node   = this.first_node.parentNode
        this.Remove(true)
        this.DeleteChildBlocks()
        this.CreateNodes(this.fragment)
        this.first_node = this.fragment.firstChild || null
        this.last_node  = this.fragment.lastChild || null
        parent_node.insertBefore(this.fragment, before_node)
    }
    LiveBlock.prototype.Remove = function()
    {
        var current_node = this.first_node
        var next_node    = this.last_node.nextSibling
        while ( current_node != next_node )
        {
            var next_sibling = current_node.nextSibling || null
            current_node.parentNode.removeChild(current_node)
            current_node = next_sibling
        }
        this.first_node = null
        this.last_node  = null
    }
    LiveBlock.prototype.Delete = function()
    {
        this.live_var.UnRegister(this)
        this.DeleteObjects()
        this.first_node = this.last_node = null
    }
    LiveBlock.prototype.DeleteLiveObjects = function()
    {
        var i = this.registered_objects.length
        while ( i-- )
        {
            this.registered_objects[i].Delete()
            delete this.registered_objects[i]
        }
        this.registered_objects = []
    }
    LiveBlock.prototype.RegisterLiveObject = function(LiveObject)
    {
        if ( LiveObject.live_var ) this.registered_objects[this.registered_objects.length] = LiveObject
        return LiveObject
    }
    LiveBlock.prototype.NewChildBlock = function(Type, Params)
    {
        return this.RegisterLiveObject(new LiveBlocks[Type](Params))
    }
    LiveBlock.prototype.NewLiveAttribute = function(LiveVariable)
    {
        return this.RegisterLiveObject(new LiveAttribute(LiveVariable))
    }
    LiveBlock.prototype.NewLiveText = function(LiveVariable)
    {
        return this.RegisterLiveObject(new LiveText(LiveVariable))
    }
    LiveBlock.prototype.NewLiveExpresion = function(GetExpression)
    {
        return this.RegisterLiveObject(new LiveExpression(GetExpression))
    }
    LiveBlock.prototype.c = LiveBlock.prototype.NewChildBlock
    LiveBlock.prototype.a = LiveBlock.prototype.NewLiveAttribute
    LiveBlock.prototype.t = LiveBlock.prototype.NewLiveText
    LiveBlock.prototype.e = LiveBlock.prototype.NewLiveExpression
    
    
    /*
     * LIVE ECHO BLOCK
     */
    var EchoBlock = function(Params)
    {
        this.RegisterVar(Params.live_var)
    }
    EchoBlock.prototype = new LiveBlock()
    EchoBlock.prototype.CreateNodes = function(ParentNode)
    {
        ParentNode.appendChild(document.createTextNode(this.value))
    }
    EchoBlock.prototype.Set = function(Value)
    {
        this.first_node.data = Value
    }
    /*
     * IF BLOCK
     */
    var IfBlock = function(Params)
    {
        this.RegisterVar(Params.expression)
        this.CreateOnTrue  = Params.true_content
        this.CreateOnFalse = Params.false_content || null
    }
    IfBlock.prototype = new LiveBlock()
    IfBlock.prototype.CreateNodes = function(ParentNode)
    {
        if ( this.value )
        {
            this.CreateOnTrue(ParentNode)
        }
        else if ( this.CreateOnFalse )
        {
             this.CreateOnFalse(ParentNode)
        }
    }
    /**
     * FOREACH BLOCK
     */
    var ForeachBlock = function(Params)
    {
        this.RegisterVar(Params.live_var)
        this.CreateLoopNodes = Params.content
        this.value_var_name  = Params.value
        this.index_var_name  = Params.index || 'i'
        this.$[this.value_var_name] = ''
    }
    ForeachBlock.prototype = new LiveBlock()
    ForeachBlock.prototype.CreateNodes = function(ParentNode)
    {
        for ( var i in this.value )
        {
            this.$[this.value_var_name] = this.live_var[i] || this.value[i]
            this.$[this.index_var_name] = i
            var new_loop_block = this.NewChildBlock('template', this.CreateLoopNodes)
            ParentNode.AppendBlock(new_loop_block)
        }
    }
    ForeachBlock.prototype.Add = function(Index, Value)
    {
        var parent_node = this.first_node.parentNode
        var next_node   = this.last_node.nextSibling
        this.$[this.value_var_name] = this.live_var[Index] || this.value[Index]
        this.$[this.index_var_name] = Index
        var new_loop_block = this.NewChildBlock('template', this.CreateLoopNodes)
        new_loop_block.Create()
        parent_node.AppendBlock(new_loop_block, next_node)
        this.last_node = next_node.previousSibling
    }
    /**
     * INCLUDE
     */
    var IncludeBlock = function(Params)
    {
        this.RegisterVar(Params.href)
    }
    IncludeBlock.prototype = new LiveBlock()
    IncludeBlock.prototype.CreateNodes = function(ParentNode)
    {
        var block = this
        var Onload = function(Contents)
        {
            eval('block.CreateNodes = function(p){\n'+Contents+'\n}')
            //block.CreateNodes = new Function('p',Contents)
            var parent_node = ( block.first_node ) ? block.first_node.parentNode : ParentNode
            block.CreateNodes(parent_node)
        }
        LoadFile(this.value, Onload)
    }
    /**
     * Short cuts
     */
    var LiveBlocks = {
        'template':LiveBlock,
        'echo':EchoBlock,
        'if':IfBlock,
        'foreach':ForeachBlock,
        'include':IncludeBlock
    }

    /**
     * Live template main functions
     */
    this.SetTemplate = function(CreateNodes)
    {
        this.main_block = new LiveBlock(CreateNodes)
    }
    this.LoadTemplate = function(TemplateName)
    {
        this.main_block = new IncludeBlock({href:TemplateName})
    }
    this.Start = function(ParentNode)
    {
        if ( !this.main_block ) this.main_block = new IncludeBlock({href:$.main_template})
        this.main_block.id = GetLiveObjectId()
        this.main_block.Create(ParentNode)
        //var parent_node = ParentNode || document.body
        //parent_node.AppendBlock(this.main_block)
    }
    this.GetLiveVar = function(Value)
    {
        if ( Value ) $.Set(Value)
        return $
    }
}